#include <rtgui/rtgui.h>
#include <rtgui/dc.h>
#include <rtgui/rtgui_system.h>
#include <rtgui/widgets/window.h>

#include "touch.h"

#define CALIBRATION_STEP_LEFTTOP        0
#define CALIBRATION_STEP_RIGHTTOP       1
#define CALIBRATION_STEP_RIGHTBOTTOM    2
#define CALIBRATION_STEP_LEFTBOTTOM     3
#define CALIBRATION_STEP_CENTER         4

#define TOUCH_WIN_UPDATE                1
#define TOUCH_WIN_CLOSE                 2

#define CALIBRATION_WIDTH               15
#define CALIBRATION_HEIGHT              15

struct calibration_session
{
    rt_uint8_t step;

    struct calibration_data data;

    rt_uint16_t width;
    rt_uint16_t height;

    rt_device_t device;
    rt_thread_t tid;
};
static struct calibration_session *calibration_ptr = RT_NULL;

static void calibration_data_post(rt_uint16_t x, rt_uint16_t y)
{
    if (calibration_ptr != RT_NULL)
    {
        switch (calibration_ptr->step)
        {
        case CALIBRATION_STEP_LEFTTOP:
            calibration_ptr->data.min_x = x;
            calibration_ptr->data.min_y = y;
            break;

        case CALIBRATION_STEP_RIGHTTOP:
            calibration_ptr->data.max_x = x;
            calibration_ptr->data.min_y = (calibration_ptr->data.min_y + y) / 2;
            break;

        case CALIBRATION_STEP_LEFTBOTTOM:
            calibration_ptr->data.min_x = (calibration_ptr->data.min_x + x) / 2;
            calibration_ptr->data.max_y = y;
            break;

        case CALIBRATION_STEP_RIGHTBOTTOM:
            calibration_ptr->data.max_x = (calibration_ptr->data.max_x + x) / 2;
            calibration_ptr->data.max_y = (calibration_ptr->data.max_y + y) / 2;
            break;

        case CALIBRATION_STEP_CENTER:
            /* calibration done */
        {
            rt_uint16_t w, h;

            struct rtgui_event_command ecmd;
            RTGUI_EVENT_COMMAND_INIT(&ecmd);
            ecmd.command_id = TOUCH_WIN_CLOSE;

            /* calculate calibrated data */
            if (calibration_ptr->data.max_x > calibration_ptr->data.min_x)
                w = calibration_ptr->data.max_x - calibration_ptr->data.min_x;
            else
                w = calibration_ptr->data.min_x - calibration_ptr->data.max_x;
            w = (w / (calibration_ptr->width - 2 * CALIBRATION_WIDTH)) * CALIBRATION_WIDTH;

            if (calibration_ptr->data.max_y > calibration_ptr->data.min_y)
                h = calibration_ptr->data.max_y - calibration_ptr->data.min_y;
            else
                h = calibration_ptr->data.min_y - calibration_ptr->data.max_y;
            h = (h / (calibration_ptr->height - 2 * CALIBRATION_HEIGHT)) * CALIBRATION_HEIGHT;

            rt_kprintf("w: %d, h: %d\n", w, h);

            if (calibration_ptr->data.max_x > calibration_ptr->data.min_x)
            {
                calibration_ptr->data.min_x -= w;
                calibration_ptr->data.max_x += w;
            }
            else
            {
                calibration_ptr->data.min_x += w;
                calibration_ptr->data.max_x -= w;
            }

            if (calibration_ptr->data.max_y > calibration_ptr->data.min_y)
            {
                calibration_ptr->data.min_y -= h;
                calibration_ptr->data.max_y += h;
            }
            else
            {
                calibration_ptr->data.min_y += h;
                calibration_ptr->data.max_y -= h;
            }

            rt_kprintf("calibration data: (%d, %d), (%d, %d)\n",
                       calibration_ptr->data.min_x,
                       calibration_ptr->data.max_x,
                       calibration_ptr->data.min_y,
                       calibration_ptr->data.max_y);
            rtgui_thread_send(calibration_ptr->tid, &ecmd.parent, sizeof(struct rtgui_event_command));
        }
        return;
        }

        calibration_ptr->step ++;

        /* post command event */
        {
            struct rtgui_event_command ecmd;
            RTGUI_EVENT_COMMAND_INIT(&ecmd);
            ecmd.command_id = TOUCH_WIN_UPDATE;

            rtgui_thread_send(calibration_ptr->tid, &ecmd.parent, sizeof(struct rtgui_event_command));
        }
    }
}

rt_bool_t calibration_event_handler(struct rtgui_widget *widget, struct rtgui_event *event)
{
    switch (event->type)
    {
    case RTGUI_EVENT_PAINT:
    {
        struct rtgui_dc *dc;
        struct rtgui_rect rect;

        dc = rtgui_dc_begin_drawing(widget);
        if (dc == RT_NULL) break;

        /* get rect information */
        rtgui_widget_get_rect(widget, &rect);

        /* clear whole window */
        RTGUI_WIDGET_BACKGROUND(widget) = white;
        rtgui_dc_fill_rect(dc, &rect);

        /* reset color */
        RTGUI_WIDGET_BACKGROUND(widget) = green;
        RTGUI_WIDGET_FOREGROUND(widget) = black;

        switch (calibration_ptr->step)
        {
        case CALIBRATION_STEP_LEFTTOP:
            rtgui_dc_draw_hline(dc, 0, 2 * CALIBRATION_WIDTH, CALIBRATION_HEIGHT);
            rtgui_dc_draw_vline(dc, CALIBRATION_WIDTH, 0, 2 * CALIBRATION_HEIGHT);
            RTGUI_WIDGET_FOREGROUND(widget) = red;
            rtgui_dc_fill_circle(dc, CALIBRATION_WIDTH, CALIBRATION_HEIGHT, 4);
            break;

        case CALIBRATION_STEP_RIGHTTOP:
            rtgui_dc_draw_hline(dc, calibration_ptr->width - 2 * CALIBRATION_WIDTH,
                                calibration_ptr->width, CALIBRATION_HEIGHT);
            rtgui_dc_draw_vline(dc, calibration_ptr->width - CALIBRATION_WIDTH, 0, 2 * CALIBRATION_HEIGHT);
            RTGUI_WIDGET_FOREGROUND(widget) = red;
            rtgui_dc_fill_circle(dc, calibration_ptr->width - CALIBRATION_WIDTH, CALIBRATION_HEIGHT, 4);
            break;

        case CALIBRATION_STEP_LEFTBOTTOM:
            rtgui_dc_draw_hline(dc, 0, 2 * CALIBRATION_WIDTH, calibration_ptr->height - CALIBRATION_HEIGHT);
            rtgui_dc_draw_vline(dc, CALIBRATION_WIDTH, calibration_ptr->height - 2 * CALIBRATION_HEIGHT, calibration_ptr->height);
            RTGUI_WIDGET_FOREGROUND(widget) = red;
            rtgui_dc_fill_circle(dc, CALIBRATION_WIDTH, calibration_ptr->height - CALIBRATION_HEIGHT, 4);
            break;

        case CALIBRATION_STEP_RIGHTBOTTOM:
            rtgui_dc_draw_hline(dc, calibration_ptr->width - 2 * CALIBRATION_WIDTH,
                                calibration_ptr->width, calibration_ptr->height - CALIBRATION_HEIGHT);
            rtgui_dc_draw_vline(dc, calibration_ptr->width - CALIBRATION_WIDTH, calibration_ptr->height - 2 * CALIBRATION_HEIGHT, calibration_ptr->height);
            RTGUI_WIDGET_FOREGROUND(widget) = red;
            rtgui_dc_fill_circle(dc, calibration_ptr->width - CALIBRATION_WIDTH, calibration_ptr->height - CALIBRATION_HEIGHT, 4);
            break;

        case CALIBRATION_STEP_CENTER:
            rtgui_dc_draw_hline(dc, calibration_ptr->width / 2 - CALIBRATION_WIDTH, calibration_ptr->width / 2 + CALIBRATION_WIDTH, calibration_ptr->height / 2);
            rtgui_dc_draw_vline(dc, calibration_ptr->width / 2, calibration_ptr->height / 2 - CALIBRATION_HEIGHT, calibration_ptr->height / 2 + CALIBRATION_HEIGHT);
            RTGUI_WIDGET_FOREGROUND(widget) = red;
            rtgui_dc_fill_circle(dc, calibration_ptr->width / 2, calibration_ptr->height / 2, 4);
            break;
        }
        rtgui_dc_end_drawing(dc);
    }
    break;

    case RTGUI_EVENT_COMMAND:
    {
        struct rtgui_event_command *ecmd = (struct rtgui_event_command *)event;

        switch (ecmd->command_id)
        {
        case TOUCH_WIN_UPDATE:
            rtgui_widget_update(widget);
            break;
        case TOUCH_WIN_CLOSE:
            rtgui_win_close(RTGUI_WIN(widget));
            break;
        }
    }
    return RT_TRUE;

    default:
        rtgui_win_event_handler(widget, event);
    }

    return RT_FALSE;
}

void calibration_entry(void *parameter)
{
    rt_mq_t mq;
    rtgui_win_t *win;
    struct rtgui_rect rect;

    mq = rt_mq_create("cali", 40, 8, RT_IPC_FLAG_FIFO);
    if (mq == RT_NULL) return;

    rtgui_thread_register(rt_thread_self(), mq);

    rtgui_graphic_driver_get_rect(rtgui_graphic_driver_get_default(), &rect);

    /* set screen rect */
    calibration_ptr->width = rect.x2;
    calibration_ptr->height = rect.y2;

    /* create calibration window */
    win = rtgui_win_create(RT_NULL,
                           "calibration", &rect, RTGUI_WIN_STYLE_NO_TITLE | RTGUI_WIN_STYLE_NO_BORDER);
    rtgui_widget_set_event_handler(RTGUI_WIDGET(win), calibration_event_handler);
    if (win != RT_NULL)
    {
        rtgui_win_show(win, RT_FALSE);
        // rtgui_widget_update(RTGUI_WIDGET(win));
        rtgui_win_event_loop(win);
    }

    rtgui_thread_deregister(rt_thread_self());
    rt_mq_delete(mq);

    /* set calibration data */
    rt_device_control(calibration_ptr->device, RT_TOUCH_CALIBRATION_DATA, &calibration_ptr->data);

    /* recover to normal */
    rt_device_control(calibration_ptr->device, RT_TOUCH_NORMAL, RT_NULL);

    /* release memory */
    rt_free(calibration_ptr);
    calibration_ptr = RT_NULL;
}

void calibration_init()
{
    rt_device_t device;

    device = rt_device_find("touch");
    if (device == RT_NULL) return; /* no this device */

    calibration_ptr = (struct calibration_session *)rt_malloc(sizeof(struct calibration_session));
    rt_memset(calibration_ptr, 0, sizeof(struct calibration_data));
    calibration_ptr->device = device;

    rt_device_control(calibration_ptr->device, RT_TOUCH_CALIBRATION, (void *)calibration_data_post);

    calibration_ptr->tid = rt_thread_create("cali", calibration_entry, RT_NULL,
                                            2048, 20, 5);
    if (calibration_ptr->tid != RT_NULL) rt_thread_startup(calibration_ptr->tid);
}

#ifdef RT_USING_FINSH
#include <finsh.h>
void calibration()
{
    calibration_init();
}
FINSH_FUNCTION_EXPORT(calibration, perform touch calibration);
#endif
